package com.ybwh.datasource.refreshable.core.datasource;


import com.ybwh.datasource.refreshable.core.RefreshableResource;
import com.ybwh.datasource.refreshable.core.ResourceHolder;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

/**
 * <p>
 * 委托方式实现的可刷新数据源.
 * 子类只需实现 createDataSource 和 destroyDataSource 逻辑
 * 例如：
 * <pre>
 *      @Data
 *      public class MyRefreshDataSource extends AbstractDelegatedRefreshResource {
 *          ... properties  declaration
 *
 *          @Override
 *          protected DataSource createDataSource() throws SQLException {
 *              //创建数据源逻辑
 *
 *              return null;
 *          }
 *
 *          @Override
 *          protected void destroyDataSource(DataSource dataSource) throws Exception {
 *              //销毁数据源逻辑
 *          }
 *       }
 * </pre>
 * <p>
 * spring 中使用需要指定init和destroy 方法：
 * 例如:
 * <pre>
 *     <bean class="xxx.xxx.MyRefreshDataSource" init-method="init" destroy-method="destroy">
 *         ... properties inject
 *      </bean>
 *
 * </pre>
 *
 *
 * </p>
 *
 * @author fanbeibei
 * @version V1.0
 * @since 2019/4/5 21:41
 */
@Slf4j
public abstract class AbstractDelegatedRefreshDataSource implements RefreshableResource, DataSource {
    /**
     * 旧Datasource存活时间
     */
    protected static final long OLD_DATASOURCE_IDEL = 60 * 60 * 1000;
    //下面测试用
//    protected static final long OLD_RESOURCE_IDEL = 10 * 1000;


    private volatile boolean inited = false;
    private volatile boolean closed = false;

    protected final ResourceHolder<DataSource> dataSourceHolder = new ResourceHolder();


    /**
     * 时间戳 -> DataSource
     */
    protected final ConcurrentHashMap<Long, DataSource> oldDataSourceMap = new ConcurrentHashMap<>();

    protected ScheduledExecutorService closeDatasourceScheduledService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
        private final AtomicLong threadIndex = new AtomicLong(0);
        private final String THREAD_NAME_PREFIX = "AbstractDelegatedRefreshResource-thread-";

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, THREAD_NAME_PREFIX + this.threadIndex.incrementAndGet());

        }
    });


    /**
     * 初始化逻辑
     */
    public synchronized void init() {
        //已经做过初始化
        if (inited) {
            return;
        }

        inited = true;

        try {
            dataSourceHolder.set(createDataSource());
            closeDatasourceScheduledService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    releaseOldDataSources();
                }
            }, OLD_DATASOURCE_IDEL / 2, OLD_DATASOURCE_IDEL / 2, TimeUnit.MILLISECONDS);
        } catch (SQLException e) {
            log.error("init exception!", e);
        }
    }


    /**
     * 销毁释放资源逻辑
     */
    public synchronized void destroy() {
        if (closed) {
            return;
        }

        closed = true;

        try {
            closeDatasourceScheduledService.shutdown();
            destroyDataSource(dataSourceHolder.get());
            releaseOldDataSources();
        } catch (Exception e) {
            log.error("destroy dataSource error", e);
        }
    }


    @Override
    public synchronized void refresh() throws SQLException {
        beforeRefresh();

        log.info("refresh method invoke!!");
        //这里refresh执行频率很低且是串行执行的，所以不可能造成oldDataSourceMap的key覆盖
        oldDataSourceMap.put(System.currentTimeMillis(), dataSourceHolder.get());
        dataSourceHolder.set(createDataSource());

        afterRefresh();
    }


    /**
     * 销毁旧数据源
     */
    private void releaseOldDataSources() {
        log.debug("releaseOldDataSources invoke! ");
        /**
         * 定期延时关闭老的DataSource,由于这里DataSource虽被刷新替换,但人有可能有业务线程（比如批量导入）在使用，
         * 所以需要延期关闭。
         *
         */
        for (Map.Entry<Long, DataSource> entry : oldDataSourceMap.entrySet()) {
            DataSource oldDataSource = entry.getValue();
            Long replacedTimestamp = entry.getKey();
            long idelTime = System.currentTimeMillis() - replacedTimestamp;

            if (idelTime >= OLD_DATASOURCE_IDEL) {


                oldDataSourceMap.remove(replacedTimestamp);
                try {
                    log.debug("destroy datasource,{}", oldDataSource.toString());
                    destroyDataSource(oldDataSource);
                } catch (Exception e) {
                    log.error("destroy dataSource error", e);
                }

            }

        }
    }

    /**
     * 创建数据源逻辑
     *
     * @return 数据源
     * @throws SQLException 创建失败
     */
    protected abstract DataSource createDataSource() throws SQLException;

    /**
     * 销毁数据源逻辑
     *
     * @param dataSource 数据源逻辑
     * @throws Exception 销毁失败
     */
    protected abstract void destroyDataSource(DataSource dataSource) throws Exception;


    /*----------------以下是DataSource接口的方法-------------------------------------------------------------------------------*/

    private PrintWriter logWriter = new PrintWriter(System.out);

    @Override
    public synchronized Connection getConnection() throws SQLException {
        if (!inited) {
            log.error("dataSource has been inited!");
            throw new IllegalStateException("dataSource has been inited!");
        }

        if (closed) {
            log.error("dataSource has been closed!");
            throw new IllegalStateException("dataSource has been closed!");
        }


        return dataSourceHolder.get().getConnection();
    }

    @Override
    public synchronized Connection getConnection(String username, String password) throws SQLException {
        if (!inited) {
            log.error("dataSource has been inited!");
            throw new IllegalStateException("dataSource has been inited!");
        }

        if (closed) {
            log.error("dataSource has been closed!");
            throw new IllegalStateException("dataSource has been closed!");
        }
        return dataSourceHolder.get().getConnection(username, password);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new UnsupportedOperationException("unsupported unwrap(Class<T> iface)");
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        throw new UnsupportedOperationException("unsupported isWrapperFor(Class<?> iface)");
    }

    @Override
    public final PrintWriter getLogWriter() throws SQLException {
        return logWriter;
    }

    @Override
    public final void setLogWriter(final PrintWriter out) throws SQLException {
        this.logWriter = out;
    }

    @Override
    public final Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }


    @Override
    public final int getLoginTimeout() throws SQLException {
        throw new SQLFeatureNotSupportedException("unsupported getLoginTimeout()");
    }

    @Override
    public final void setLoginTimeout(final int seconds) throws SQLException {
        throw new SQLFeatureNotSupportedException("unsupported setLoginTimeout(int seconds)");
    }


}
